#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <RTClib.h>
#include <SparkFunTSL2561.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <WiFi.h>

#define PIN_NO_WR 21
#define PIN_NO_CD 22
#define PIN_NO_CSS 25
#define PIN_NO_RES 27

#define PIN_NO_D0 14
#define PIN_NO_D1 13
#define PIN_NO_D2 26
#define PIN_NO_D3 18
#define PIN_NO_D4 19
#define PIN_NO_D5 23
#define PIN_NO_D6 5
#define PIN_NO_D7 33

#define PIN_PWR   2

#define PIN_SDA 32
#define PIN_SCL 4

#define PIN_SQW 34
#define PIN_MOVE  36


U8G2_GU800_128X64_F_8080 u8g2(U8G2_R0, PIN_NO_D0, PIN_NO_D1, PIN_NO_D2, PIN_NO_D3, PIN_NO_D4, PIN_NO_D5, PIN_NO_D6, PIN_NO_D7, PIN_NO_WR, PIN_NO_CSS, PIN_NO_CD, PIN_NO_RES);

RTC_DS3231 rtc;

SFE_TSL2561 light;
boolean gain;     // Gain setting, 0 = X1, 1 = X16;
unsigned int ms;  // Integration ("shutter") time in milliseconds

const char *ssid     = "Becky2.4G";
const char *password = "chengchiuman";

WiFiUDP ntpUDP;

// You can specify the time server pool and the offset (in seconds, can be
// changed later with setTimeOffset() ). Additionaly you can specify the
// update interval (in milliseconds, can be changed using setUpdateInterval() ).
NTPClient timeClient(ntpUDP, "cn.pool.ntp.org", 8 * 3600);

uint32_t    lastCheck = 999 * 60;
DateTime    lastModify;

const uint8_t bdf_font[562] U8G2_FONT_SECTION("bdf_font") = 
  "\14\0\5\5\5\6\5\1\6\30 \0\0\37\367#\366\0\0\0\0\2\25 \5\0\200u\60\71\24\234"
  "uT\60%\65\346!\320\70\212!\214S\210\2\261\322\12\61\247)\17\201F\21\226`\210h<\305\61"
  "\14Z\322\32\62B\201\214`\30c\30\344\30Pq\16\243\222\2\0\61\23\355\243\365%\240\242 \343\3"
  "\327\70L\201\312\377\377\7\62*\25\224uT\264\5\71\6\42\322\70\212!\14d\4\3\31\241PE("
  "T\1\213gr\306\63\234\311\35\355<g\236\373\300\7,\63\67\25\224uT\264\5=%\42\222\70\312"
  "!\214c\4#\61\311x\246W<\243\35iQ\312R\26\343\220g@\343\31\260\200\213ZRs\216p"
  "\224cH#\62\215R\12\0\64\66\367\213\365fD\3\36\17\345\222\246bE(V!\212U\210B\25"
  "\243P\4)RA\12T\224\2\25\245\70\205)\235\302\24\247(\5*\310\7>\340\271B\226\217\0\65"
  "\65\365\233u!\15y\243\200\245gzE\11T\21\22\3\225\210H\303(\207\11\222\361\14X\202\306["
  "T\21\12U\4\3\31\341\70\206\70\310A\42\2\235F%\6\0\66A\25\224u\30\270\203%jI\14"
  "R\24\322\14g<\343\25\317\30\305)DR\212\260\20iL\1\61F\70\20\223\30\265RE(T\21"
  "\12U\4#\25\301@\206\60\214\61\16r\20\310<\207YI\1\0\67-\365\223u\360\3\26,^\1"
  "\213W\300\342\31\257x\306+\236\361\212g\274\2\26\317x\305\63^\361\214W<\223\63\236\341\214g\70"
  "\203\1\70;\24\234uD\64\5\71\5\32\222\70\310!\14c\4\3\261!#\24\250\20\206\61\306!\216"
  "\342\34\6\61\6\32\7\71\204a\214`\240E\251\251\5\61\310\10\7A\202\64\240\342 \245\0\71:\25"
  "\224uT\270D\65\347)\21\61\212\61\214c\10\3\25\242HE\60\22\273d\204\42\31\301@F\70\314"
  "!fC*I(P\61\12X<\303\31\356hGZ)e!\315P\0:\23(\323uA\14#|"
  "\302\370\240N\14#|\302\10\0\0\0\0\4\377\377\0";

  const char*   strMonth[] = {
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec"
  };


void onAlarm() {
  lastCheck++;
  // Serial.println("......Alarm.....");
}

void onMove() {
  // Serial.println("......Move.....");
}

const char* WeekdayStr(uint8_t w) {
  switch (w)
  {
    case 0:
      return "Sun";
    case 1:
      return "Mon";
    case 2:
      return "Tue";
    case 3:
      return "Wed";
    case 4:
      return "Thu";
    case 5:
      return "Fri";
    case 6:
      return "Sat";
  }

  return "";
}


void Display(const DateTime& t) {
    char    time[6];
    char    strMon[4];
    char    strDay[3];
    char    strCheck[10];
    char    strModify[10];
    const char*    ampm = "AM";
    uint8_t h, m, w, month, day;

    h = t.hour();
    m = t.minute();
    w = t.dayOfTheWeek();

    month = t.month();
    day = t.day();

    if (h > 12) {
      h = h - 12;
      ampm = "PM";
    } else if (h == 12) {
      ampm = "PM";
    }

    if (h == 0) {
      h = 12;
    }

    sprintf(time, "%02d:%02d", h, m);
    sprintf(strMon, "%s", strMonth[month - 1]);
    sprintf(strDay, "%02d", day);

    sprintf(strCheck, "C:%dh", lastCheck / 60);
    sprintf(strModify, "W:%02d/%02d", lastModify.month(), lastModify.day());

    u8g2.firstPage();
    // u8g2.setDrawColor();
    u8g2.setDrawColor(1);

    u8g2.setFont(u8g2_font_9x15_mr);
    u8g2.drawStr(0, 11, ampm);

    u8g2.setFont(bdf_font);
    u8g2.drawStr(20, 32, time);

    u8g2.setFont(u8g2_font_chargen_92_mr);
    u8g2.setDrawColor(0);
    u8g2.drawStr(210, 16, WeekdayStr(w));

    // u8g2.setFont(u8g2_font_10x20_mr);
    u8g2.setDrawColor(1);
    u8g2.drawStr(167, 14, strDay);
    u8g2.drawStr(162, 32, strMon);
    u8g2.drawHLine(162, 15, 36);
    u8g2.drawHLine(162, 16, 36);

    // last check time & last correct date
    u8g2.setFont(u8g2_font_5x8_mr);
    u8g2.drawStr(210,25, strCheck);

    u8g2.drawStr(210, 32, strModify);

    u8g2.updateDisplay();
}

void TurnOnDisplay() {
  digitalWrite(PIN_PWR, 1);
  delay(100);

  u8g2.begin();

  Display(rtc.now());
}

void TurnOffDisplay() {
  digitalWrite(PIN_PWR, 0);
}

void setup(void)
{
  pinMode(PIN_PWR, OUTPUT);
  digitalWrite(PIN_PWR, 1);
  delay(100);

  // Serial.begin(115200);

  u8g2.begin();

  u8g2.clearDisplay();

  Wire.begin(PIN_SDA, PIN_SCL);

  // RTC
  if (!rtc.begin())
  {
    // Serial.println("Couldn't find RTC");
    // Serial.flush();
    while (1)
      delay(10);
  }

  if (rtc.lostPower()) {
    // Serial.println("RTC lost power, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  rtc.disable32K();

  pinMode(PIN_SQW, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_SQW), onAlarm, FALLING);

  esp_sleep_enable_ext0_wakeup(GPIO_NUM_34, 0);

  rtc.writeSqwPinMode(DS3231_OFF);

  rtc.clearAlarm(1);
  rtc.clearAlarm(2);

  rtc.disableAlarm(1);

  Display(rtc.now());

  rtc.setAlarm2(rtc.now(), DS3231_A2_PerMinute);

  lastModify = rtc.now();

  // Move
  pinMode(PIN_MOVE, INPUT);
  attachInterrupt(digitalPinToInterrupt(PIN_MOVE), onMove, RISING);

  // esp_sleep_enable_ext1_wakeup( uint64_t(1) << PIN_MOVE, ESP_EXT1_WAKEUP_ANY_HIGH);


    // TSL2561
  light.begin();
  
  unsigned char ID;
  
  // if (light.getID(ID))
  // {
  //   Serial.print("Got factory ID: 0X");
  //   Serial.print(ID,HEX);
  //   Serial.println(", should be 0X5X");
  // }

  // The light sensor has a default integration time of 402ms,
  // and a default gain of low (1X).
  
  // If you would like to change either of these, you can
  // do so using the setTiming() command.
  
  // If gain = false (0), device is set to low gain (1X)
  // If gain = high (1), device is set to high gain (16X)

  gain = 1;

  // If time = 0, integration will be 13.7ms
  // If time = 1, integration will be 101ms
  // If time = 2, integration will be 402ms
  // If time = 3, use manual start / stop to perform your own integration

  unsigned char time = 2;

  // setTiming() will set the third parameter (ms) to the
  // requested integration time in ms (this will be useful later):
  
  // Serial.println("Set timing...");
  light.setTiming(gain,time,ms);

  // To start taking measurements, power up the sensor:
  
  // Serial.println("Powerup...");
  light.setPowerUp();

  // Serial.flush();


  u8g2.setContrast(255);
}

bool SyncTime() {
  bool ChangeTime = false;

  // Serial.println("sync start...");

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  // Serial.print("Connecting wifi...");

  for (int i = 0; i < 30; i++) {
    delay(500);
    if (WiFi.status() == WL_CONNECTED) {
      break;
    }
    // Serial.print('.');
  }

  if (WiFi.status() != WL_CONNECTED) {
    // Serial.println("");
    WiFi.disconnect(true, true);
    return false;
  }

  // Serial.println("Connected");

  timeClient.begin();

  if (timeClient.update()) {
    // Serial.println("NTP client update successfully.");
    lastCheck = 0;

    // Check HW RTC (误差超过20秒才同步)
    DateTime    ntpTime(timeClient.getEpochTime());
    TimeSpan    diff;
    int32_t     diffSeconds;

    diff = rtc.now() - ntpTime;

    diffSeconds = diff.totalseconds();

    // Serial.print("Diff second = ");
    // Serial.println(diffSeconds);

    if (abs(diffSeconds) >= 20) {
      lastModify = ntpTime;

      // Serial.println("Adjust HW RTC.");
      // Set to HW RTC
      rtc.adjust(ntpTime);

      ChangeTime = true;
    }

    // Serial.println(timeClient.getFormattedTime());
    // Serial.flush();
  }

  WiFi.disconnect(true, true);

  return ChangeTime;
}

void loop(void)
{
  static uint32_t   lastCheck_update = 0;
  static uint32_t   noMoveCount = 0;
  static bool       displayOn = true;

  esp_sleep_enable_timer_wakeup(1 * 1000 * 1000);
  esp_light_sleep_start();

  if (lastCheck_update != lastCheck) {
    rtc.clearAlarm(2);
    Display(rtc.now());
    lastCheck_update = lastCheck;
  }
  
  if (digitalRead(PIN_MOVE) == 1) {
    noMoveCount = 0;
    if (!displayOn) {
      // Serial.println("+++++++Turn on------");
      // Serial.flush();

      TurnOnDisplay();

      displayOn = true;
    }
  } else {
    if (displayOn) {
      noMoveCount++;
    }
  }

  if (noMoveCount >= 15 * 60) {
    // Serial.println("--------Turn off-----");
    // Serial.flush();

    TurnOffDisplay();

    displayOn = false;
    noMoveCount = 0;
  }

  // 检查环境照度（当前就暂定两个等级）
  unsigned int data0, data1;
  
  if (light.getData(data0,data1))
  {
    double lux;    // Resulting lux value
    boolean good;  // True if neither sensor is saturated
    
    // Perform lux calculation:

    good = light.getLux(gain,ms,data0,data1,lux);
    
    // Serial.print(" lux: ");
    // Serial.print(lux);
    // if (good) Serial.println(" (good)"); else Serial.println(" (BAD)");
    // Serial.flush();

    if (lux < 0.5) {
      u8g2.setContrast(0);
    }

    if (lux > 0.8) {
      u8g2.setContrast(255);
    }
  }

  if (lastCheck > 24 * 60) {
    if (SyncTime()) {
      Display(rtc.now());
    }
  }
}